/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.client.dialog.ac.model;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import offset.nodes.Constants;
import offset.nodes.client.model.Server;
import offset.nodes.client.virtual.model.jcr.NodeReader;
import offset.nodes.client.virtual.model.jcr.NodeWriter;
import offset.nodes.client.virtual.model.jcr.PrintHandler;
import offset.nodes.client.virtual.model.jcr.SimpleNode;
import offset.nodes.client.dialog.ac.view.Principal;
import offset.nodes.client.dialog.ac.view.Privilege;
import offset.nodes.client.dialog.ac.view.Role;
import offset.nodes.client.dialog.ac.view.User;
import org.xml.sax.SAXException;

/**
 *
 * @author Walter Lütgenau
 */
public class AccessControlModel {

    Server server = null;
    GetAccessControl.Response info = null;

    public AccessControlModel(Server server) throws IOException, ClassNotFoundException {
        this.server = server;
    }

    public Server getServer() {
        return server;
    }

    protected GetAccessControl.Response getInfo(String path) throws Exception {
        if (info == null) {
            GetAccessControl.Request request = new GetAccessControl.Request();
            request.setPath(path);

            info = (GetAccessControl.Response) server.sendRequest(request);
        }

        return info;
    }

    public Collection<Principal> getInheritedAccessControlNode(String path) throws Exception {
        return getPrincipals(getInfo(path).getInheritedAccessControl(), true);
    }

    public Collection<Principal> getNodeAccessControlNode(String path) throws Exception {
        return getPrincipals(getInfo(path).getNodeAccessControl(), false);
    }

    /**
     * Return the uuid of a previously requested access control node. If there is none, return null.
     * @return the uuid of the access control node.
     * @throws Exception
     */
    protected String getUuid() throws Exception {
        if (info == null)
            return null;

        Node root = new SimpleNode("");
        NodeReader reader = new NodeReader(root);
        reader.read(new ByteArrayInputStream(info.getNodeAccessControl().getBytes()));

        Node accessControlNode = root.getNode(Constants.TYPENAME_ACCESS_CONTROL);
        if (!accessControlNode.hasProperty(Constants.JCR_UUID))
            return null;

        return accessControlNode.getProperty(Constants.JCR_UUID).getString();
    }

    public Collection<Principal> getPrincipals(String accessControlString, boolean inherited) throws Exception {
        if (accessControlString == null)
            return new LinkedList<Principal>();
        
        Node root = new SimpleNode("");
        NodeReader reader = new NodeReader(root);
        reader.read(new ByteArrayInputStream(accessControlString.getBytes()));

        Node accessControlNode = root.getNode(Constants.TYPENAME_ACCESS_CONTROL);

        LinkedList<Principal> result = new LinkedList<Principal>();
        NodeIterator aci = accessControlNode.getNodes();
        while (aci.hasNext()) {
            Node accessControlEntryNode = aci.nextNode();

            Principal principal = null;
            LinkedList<Privilege> privileges = new LinkedList<Privilege>();

            NodeIterator acei = accessControlEntryNode.getNodes();
            while (acei.hasNext()) {
                Node node = acei.nextNode();

                String type = node.getPrimaryNodeType().getName();

                if (type.equals(Constants.TYPENAME_PRINCIPAL_USER))
                    principal = new User(node.getName(), node.getProperty(Constants.PROP_REFERENCE).getString());
                else if (type.equals(Constants.TYPENAME_PRINCIPAL_ROLE))
                    principal = new Role(node.getName(), node.getProperty(Constants.PROP_REFERENCE).getString());
                else if (type.equals(Constants.TYPENAME_PRIVILEGES)) {
                    NodeIterator pi = node.getNodes();

                    while (pi.hasNext()) {
                        Node privilegeNode = pi.nextNode();
                        Privilege privilege = new Privilege(privilegeNode.getName(),
                                privilegeNode.getProperty(Constants.PROP_PRESENCE).getString().equals(Constants.VALUE_GRANTED),
                                privilegeNode.getProperty(Constants.PROP_PRESENCE).getString().equals(Constants.VALUE_DENIED));
                        privileges.add(privilege);
                    }
                }
            }

            principal.getPrivileges().addAll(privileges);
            principal.setInherited(inherited);
            result.add(principal);
        }
        return result;
    }

    public void setNodeAccessControl(String path, Collection<Principal> principals) throws Exception {

        Node accessControl = new SimpleNode(Constants.TYPENAME_ACCESS_CONTROL);
        String uuid = getUuid();
        if (uuid != null)
            accessControl.setProperty(Constants.JCR_UUID, uuid);

        Iterator<Principal> i = principals.iterator();
        while (i.hasNext()) {
            Principal principal = i.next();

            Node accessControlEntry = accessControl.addNode(Constants.TYPENAME_ACCESS_CONTROL_ENTRY);

            Node principalNode = null;
            if (principal instanceof Role) {
                principalNode = accessControlEntry.addNode(principal.getName(), Constants.TYPENAME_PRINCIPAL_ROLE);
                principalNode.setProperty(Constants.JCR_PRIMARY_TYPE, Constants.TYPENAME_PRINCIPAL_ROLE);
            } else {
                principalNode = accessControlEntry.addNode(principal.getName(), Constants.TYPENAME_PRINCIPAL_USER);
                principalNode.setProperty(Constants.JCR_PRIMARY_TYPE, Constants.TYPENAME_PRINCIPAL_USER);
            }

            principalNode.setProperty(Constants.PROP_REFERENCE, principal.getReference());

            Node privileges = accessControlEntry.addNode(Constants.TYPENAME_PRIVILEGES);

            Iterator<Privilege> j = principal.getPrivileges().iterator();
            while (j.hasNext()) {
                Privilege privilege = j.next();

                if (privilege.isGranted() || privilege.isDenied()) {
                    Node privilegeNode = privileges.addNode(privilege.getName(), Constants.TYPENAME_PRIVILEGE);

                    if (privilege.isGranted())
                        privilegeNode.setProperty(Constants.PROP_PRESENCE, Constants.VALUE_GRANTED);
                    else if (privilege.isDenied())
                        privilegeNode.setProperty(Constants.PROP_PRESENCE, Constants.VALUE_DENIED);
                }
            }

        }

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        NodeWriter writer = new NodeWriter(accessControl, new PrintHandler(out));
        writer.write();

        SetAccessControl.Request request = new SetAccessControl.Request();
        request.setNodeAccessControl(out.toString());
        request.setPath(path);

        server.sendRequest(request);
        // TODO: How to handle server negative response
    }
}
